#include "som.h"
/* Pour compiler seul : -I/usr/include/mysql -lmysqlclient -lm
Une première implémentation du SOM
Roxane Levy
 */


/* LES GLOBALES */
int N_L_input_V ;
int N ;
int map_x ;
int map_y ;
int nbIter ;


Data pointeurData ;
Data * mydata = &pointeurData ;

Net pointeurNet ;
Net * mynet = &pointeurNet ;

Options pointeurOptions;
Options * myoptions = &pointeurOptions ;


int startprog(int a,int b, int c, int d, int e) {
    N_L_input_V = a;
    N = b;
    map_x = c ;
    map_y = d ;
    nbIter = e ;
    return 0 ;
}


int destroyAll() {

    destroyData(mydata) ;
    destroyRNandMore(mynet) ;
    return 5 ;
    }


float ** getData() {
    //afficheData(data) ;
    return mydata->vecApprentissage ; }


float *** getRN() {
    //afficheRN(net) ;
    return mynet->RN;
}


float ** getActiMap() {
	return mynet->activation_map ;
}

void subIntToVector(float vec[], int s, int vectorSize) {
    int i ;
    for (i = 0 ; i < vectorSize ; i++) vec[i] = vec[i] - s ;

}

void power(float vec[], int s, int vectorSize) {
    int i ;
    for (i= 0 ; i < vectorSize; i ++) vec[i] = -pow(vec[i] , 2) ;
}

void divideVecbyFloat(float vec[], float d, int vectorSize) {
    int i ;
    for (i= 0 ; i < vectorSize ; i++) vec[i] = vec[i] / d ;
}


void normalizeVector(float vec[], int sizeVector) {
    int j ;
    float norme ;
    norme = normeVec(vec,sizeVector) ;
    for (j = 0 ; j < sizeVector ; j++) vec[j] = vec[j] / norme ;
        }


void exponentialVec(float vec[], int vectorSize) {
    int i ;
    for (i = 0 ; i < vectorSize ; i ++) vec[i] = exp(vec[i]) ;
}


void afficheNeighbourhood(Net * net) {
    int i ;
    for (i= 0 ; i < net->map_y ; i++) affiche_vecteur(net->g[i],net->map_x) ;
    }


void multiplyNeighbourhoodbyFloat(Net * net, float f) {
    int i,j ;
    for (i = 0 ; i < net->map_y ; i++) {
       for (j= 0 ; j < net->map_x ; j++)
         net->g[i][j] = net->g[i][j] * f ;
          }
}


void initialiseNeighbourhood(Net * net , float sigma) {
    float d = 2 * M_PI * sigma * sigma ;
    float neigx[net->map_x] ;
    float neigy[net->map_y]  ;
    int i ;
    for (i = 0 ; i < net->map_x ; i++) neigx[i] = i ;
    for (i = 0 ; i < net->map_y ; i++) neigy[i] = i ;
    subIntToVector(neigx,net->bmuCoord[0],net->map_x) ;
    subIntToVector(neigy,net->bmuCoord[1],net->map_y) ;
    power(neigx,2,net->map_x) ;
    power(neigy,2,net->map_y) ;
    divideVecbyFloat(neigx,d,net->map_x) ;
    divideVecbyFloat(neigy,d,net->map_y) ;
    exponentialVec(neigx,net->map_x) ;
    exponentialVec(neigy,net->map_y) ;
    int j ;
    for (i = 0 ; i < net->map_x ; i++) {
        for (j = 0 ; j < net->map_y ; j++) {
            net->g[i][j] = neigx[i] * neigy[j] ; }}

    }


void initialiseDataRandomly() ;


int main (int k, char ** argv) {

    //startprog(150,4,8,6,200) ;


    myoptions->flagshuffle = 1 ;// 0 en séquence, 1 désordonné

    mydata->N_L_input_V = N_L_input_V ;
    mydata->N = N;
    /* dimension du RN */

    mynet->neuroneSize = mydata->N ;
    mynet->map_x = map_x ;
    mynet->map_y = map_y ;


    myoptions->nb_iter =  nbIter ;
    if (myoptions->flagshuffle != 0 && myoptions->flagshuffle != 1)  {
        fprintf(stderr, "Flagcontrole doit être égal à 0 ou 1\n") ;
        exit(1) ;
    }


    /* initialisation des poids du RN ainsi que des vecteurs d'apprentissage */

    initialiseDatafromDatabase(mydata) ;



    initialiseRNandMore(mynet);


    input_random(mydata,mynet,myoptions) ;

   // destroyAll() ;



    return 0;
}

/* USELESS attention si le nb de vecteurs d'apprentissage et leur taille n'est pas initialisée*/
void initialiseDataRandomly(Data * data) {


	/* allocation de mémoire pour la matrice des vecteurs d'apprentissage */
	    int i ;
        data->vecApprentissage = (float **)malloc(sizeof(float*) * data->N_L_input_V) ;
        for (i = 0 ; i < data->N_L_input_V ; i++) data->vecApprentissage[i] = (float * ) malloc(sizeof(float) * data->N) ;

    }


/* connection to MYSQL DATABASE,  ne modifie que les données de la structure DATA, normalise les données */
void initialiseDatafromDatabase(Data * data) {
    //Déclaration du pointeur de structure de type MYSQL
    MYSQL mysql;
    //Initialisation de MySQL
    mysql_init(&mysql);
    //Options de connexion
    mysql_options(&mysql,MYSQL_READ_DEFAULT_GROUP,"option");

    //Si la connexion réussie...
    if(mysql_real_connect(&mysql,"localhost","root","ladygaga","ADPD_Database",0,NULL,0))
    {
        //Requête qui sélectionne tout dans la table concernée
        mysql_query(&mysql, "SELECT * FROM Iris");

        //Déclaration des objets
        MYSQL_RES *result = NULL;
        MYSQL_ROW row;

        int i ;
        int col ;

        //On met le jeu de résultat dans le pointeur result
        result = mysql_store_result(&mysql);

        data->N_L_input_V = mysql_num_rows(result) ;   // la taille des vecteurs d'apprentissage est récupérée

        col  = mysql_num_fields(result);   //nombre de colonnes = nombres de champs

        data->N = col - 2 ;  // first col is ID , last is target field, both are useless for the SOM


	/* allocation de mémoire pour la matrice des vecteurs d'apprentissage */
        data->vecApprentissage = (float **)malloc(sizeof(float*) * data->N_L_input_V) ;
        for (i = 0 ; i < data->N_L_input_V ; i++) data->vecApprentissage[i] = (float * ) malloc(sizeof(float) * data->N) ;

        int j = 0 ;
        while ((row = mysql_fetch_row(result)))
        {
            //On fait une boucle pour avoir la valeur de chaque champs
            for(i = 1; i < data->N+1; i++)
            {
               //On ecrit toutes les valeurs
               data->vecApprentissage[j][i-1] =  (float)strtod(row[i],NULL) ;
            }

            j+= 1 ;
        }

        //Libération du jeu de résultat
        mysql_free_result(result);

        //Fermeture de MySQL
        mysql_close(&mysql);

    }
    else  //La connexion MYSQL a échouée
    {
        printf("Une erreur s'est produite lors de la connexion à la BDD!");
    }

    /* normalisation des données */
    normalizeData(data) ;


}


/* initialise les poids du RN et les vecteurs d'apprentissage de manière aléatoire */
void initialiseRandomly(Data * data, Net * net) {
    /* allocation de mémoire pour le RN */
    net->RN = (float ***)malloc(sizeof(float**) * net->map_x) ;
    int i,j ;
    for (i = 0; i < net->map_x; i++) {
        net->RN[i] = (float**)malloc(sizeof(float*)  * net->map_y) ;
            for (j= 0 ; j < net->map_y; j++) {
                net->RN[i][j] = (float *) malloc(sizeof(float) * net->neuroneSize) ;
            }
     }

    srand (time(NULL));
    /* rempli le RN avec des poids aléatoires */
    for (i = 0; i < net->map_x; i++) {
        for (j= 0 ; j < net->map_y; j++) genereVec(net->RN[i][j], net->neuroneSize) ;
    }

    /* allocation de mémoire pour la matrice des vecteurs d'apprentissage */
    data->vecApprentissage = (float **)malloc(sizeof(float*) * data->N_L_input_V) ;
    for (i = 0 ; i < data->N_L_input_V ; i++) data->vecApprentissage[i] = (float * ) malloc(sizeof(float) * data->N) ;

    /* rempli les vecteurs d'apprentissage de manière aléatoire */
    for (i= 0 ; i < data->N_L_input_V; i++) genereVec(data->vecApprentissage[i], data->N) ;

}

/* attention version ou la fonction rempli également l'activation map, la matrice pour le voisinage*/
void initialiseRNandMore(Net * net) {

    /* allocation de mémoire */
    net->RN = (float ***)malloc(sizeof(float**) * net->map_x) ;
    int i,j ;
    for (i = 0; i < net->map_x; i++) {
        net->RN[i] = (float**)malloc(sizeof(float*)  * net->map_y) ;
            for (j= 0 ; j < net->map_y; j++) {
                net->RN[i][j] = (float *) malloc(sizeof(float) * net->neuroneSize) ;  //chaque neurones du RN est un vecteur
            }
     }

    srand (time(NULL));
    /* rempli le RN avec des poids aléatoires */
    for (i = 0; i < net->map_x; i++) {
        for (j= 0 ; j < net->map_y; j++) genereVec(net->RN[i][j], net->neuroneSize) ;
    }


    /* pour la map d'activation */
    net->activation_map = (float **)malloc(sizeof(float*) * net->map_x) ;

    for (i = 0; i < net->map_x; i++) {
        net->activation_map[i] = (float*)malloc(sizeof(float)  * net->map_y) ;
    }

    net->g = (float **)malloc(sizeof(float*) * net->map_x) ;
    for (i = 0; i < net->map_x; i++) {
        net->g[i] = (float*)malloc(sizeof(float)  * net->map_y) ;
    }



    /* normaliser le RN */
    normalizeRN(net) ;

            }


/* initialise uniquement le RN, normalise*/
void initialiseRN(Net * net) {
     /* allocation de mémoire pour le RN */
    net->RN = (float ***)malloc(sizeof(float**) * net->map_x) ;
    int i,j ;
    for (i = 0; i < net->map_x; i++) {
        net->RN[i] = (float**)malloc(sizeof(float*)  * net->map_y) ;
            for (j= 0 ; j < net->map_y; j++) {
                net->RN[i][j] = (float *) malloc(sizeof(float) * net->neuroneSize) ;  //chaque neurones du RN est un vecteur
            }
     }

    srand (time(NULL));
    /* rempli le RN avec des poids aléatoires */
    for (i = 0; i < net->map_x; i++) {
        for (j= 0 ; j < net->map_y; j++) genereVec(net->RN[i][j], net->neuroneSize) ;
    }


    /* pour la map d'activation */
    net->activation_map = (float **)malloc(sizeof(float*) * net->map_x) ;

    for (i = 0; i < net->map_x; i++) {
        net->activation_map[i] = (float*)malloc(sizeof(float)  * net->map_y) ;
    }

    normalizeRN(net) ;

            }


/* Générateur de vecteur de flottants */
void genereVec(float * vec, int size) {
    int i ;
        for (i = 0 ; i < size ; i++) vec[i] = float_aleatoire_borne(0,1) ;
}




/* retourne un flottant borné entre deux valeurs max et min passées en argument */
float float_aleatoire_borne (float min, float max) {
    return rand()/ (float)RAND_MAX * (max - min) + min;
}



/* affiche le réseau neuronal */
void afficheRN(Net * net) {
    int i,j ;
    for (i = 0; i < net->map_x; i++) {
        for (j= 0 ; j < net->map_y; j++) affiche_vecteur(net->RN[i][j], net->neuroneSize) ;
        printf("\n") ;
    }
}



/* fonction qui permet d'afficher un vecteur de float*/
void affiche_vecteur(float vector[], int tailleVecteur) {
    int i ;
    for (i = 0 ; i < tailleVecteur ; i++) printf("%.12f ",vector[i]);
    printf("\n") ;
}

/* fonction qui permet d'afficher un vecteur de float*/
void affiche_vecteurint(int vector[], int tailleVecteur) {
    int i ;
    for (i = 0 ; i < tailleVecteur ; i++) printf("%i ",vector[i]);
    printf("\n") ;
}


/* fonction qui permet d'afficher la matrice contenant les vecteurs d'apprentissage */
void afficheData(Data * data) {
    int i ;
    for (i= 0 ; i < data->N_L_input_V;i++) affiche_vecteur(data->vecApprentissage[i], data->N) ;
}


void afficheActivationMap(Net * net) {
    int i ;
    for (i= 0; i < net->map_x ; i++) affiche_vecteur(net->activation_map[i],net->map_y) ;
    printf("\n") ;
}



/* parcours itéraif qui charge les vecteurs d'apprentissage dans l'ordre */
void input_sequence(Data * data,Net * net,Options * options) {
    int iter ;
    int idx ;
    for (iter = 0 ; iter < options->nb_iter ; iter++) {   // pour le nombre d'itérations
        /* charge tous les vecteurs d'apprentissage dans l'ordre */
        printf("Itération numéro %i\n", iter+1);  //affichage de l'itération en cours
        for(idx = 0; idx < data->N_L_input_V ; idx++) {
            net->captr = *(data->vecApprentissage + idx);   //passe le pointeur sur les capteurs du RN
            findbestmatchingnode(net) ;
            update(net,options,iter) ;

        }

    }
}


void update(Net * net, Options * options,int iteration) {
    float eta = 1.0/ ( 1 + iteration/options->nb_iter) ;
    float sig = 1.0 / (1.0 + iteration/options->nb_iter) ;
    initialiseNeighbourhood(net,sig) ;
    multiplyNeighbourhoodbyFloat(net,eta) ;
    int m,n ;
    for (m = 0 ; m < net->map_x ; m++) {
        for (n = 0 ; n < net->map_y ; n ++) {
            int col = m ;
            int row = n ;
            addArray(net->RN[col][row],substractArrayandMultiply(net->captr, net->RN[col][row],net->neuroneSize,net->g[col][row]), net->neuroneSize) ;
            normalizeVector(net->RN[col][row], net->neuroneSize) ;
            }
    }
}



float * substractArrayandMultiply(float array1[], float array2[], int sizeArray, float multiplier) {
    float * r = malloc(sizeArray * sizeof(float));
    int i ;
    for (i = 0 ; i < sizeArray ; i++) {
        r[i] = array1[i] - array2[i] ;
        r[i] = r[i] * multiplier ;
        }
    return r ;
    }



/* Parcours itératif qui charge les vecteurs dans le désordre */
void input_random(Data * data, Net * net, Options * options) {
    int iter ;
    int idx ;
    int shuffle_order[data->N_L_input_V] ;
    for(idx = 0; idx < data->N_L_input_V ; idx++) shuffle_order[idx] = idx ;   // les indices ordonnées
    for (iter = 0 ; iter < options->nb_iter ; iter++) {   // pour le nombre d'itérations
        /* charge tous les vecteurs d'apprentissage dans le désordre */
        //printf("Itération numéro %i\n", iter+1); //affichage de l'itération en cours
        shuffleVec(shuffle_order,data->N_L_input_V) ;   //permute les indices
        printf("iter %i\n" , iter) ;
        /* shuffle_order contient maintenant les indices réordonnés, on affiche le parcours */
        for(idx = 0; idx < data->N_L_input_V ; idx++) {
            net->captr = *(data->vecApprentissage + shuffle_order[idx]);    //positionnement sur le capteur

            findbestmatchingnode(net) ;
            update(net,options,iter) ;
        }

    }


}




void findbestmatchingnode(Net * net){
    int i,j ;
    for (i = 0; i < net->map_x; i++) {
        for (j= 0 ; j < net->map_y; j++)
            net->activation_map[i][j] = euclidian(net->RN[i][j], net->captr, net->neuroneSize);
    }

    smallestvalue(net) ;

}


void smallestvalue(Net * net) {
    float match = net->activation_map[0][0] ;
    int i,j ;
    int min_x = 0 ;
    int min_y = 0 ;
    for (i = 0 ; i < net->map_x ; i++) {
        for (j = 0 ; j < net->map_y ; j++) {
            if (net->activation_map[i][j] < match) {
                match = net->activation_map[i][j] ;
                min_x = i ;
                min_y = j ;

            }
        }
     }
     net->bmuCoord[0] = min_x ;
     net->bmuCoord[1] = min_y ;
     }


float euclidian(float vectorMap[], float vectorData[], int sizeVec) {
    float dist = 0 ;
    int i ;
    for (i=0; i < sizeVec;i++)
        dist += (vectorData[i] - vectorMap[i]) *  (vectorData[i] - vectorMap[i]) ;
    return sqrt(dist) ;
}






void addArray(float array1[], float array2[], int sizeArray) {
    int i ;
    for (i = 0 ; i < sizeArray ; i++) {
        array1[i] += array2[i] ;
        }

}



float normeVec(float vec[] , int vectorSize) {
    float n = 0 ;
    int i ;
    for (i= 0 ; i < vectorSize ; i++) n+= pow(vec[i],2) ;
    return sqrt(n) ;
}

void normalizeData(Data * data) {
    int i,j ;
    float norme ;
    for (i = 0 ; i < data->N_L_input_V ; i++) {
        norme = normeVec(data->vecApprentissage[i],data->N) ;
        for (j = 0 ; j < data->N ; j++) data->vecApprentissage[i][j] = data->vecApprentissage[i][j] / norme ;
        }
}


void normalizeRN(Net * net) {
    int i,j,m ;
    float norme ;
    for (i = 0 ; i < net->map_x ; i++) {
        for (j = 0 ; j < net->map_y ; j++) {
            norme = normeVec(net->RN[i][j],net->neuroneSize) ;
            for (m= 0 ; m < net->neuroneSize ; m++) net->RN[i][j][m] = net->RN[i][j][m] / norme ;
             }
    }
}



/* Fonction pour le Knuth shuffle */
void shuffleVec(int sourceVecteur[], int tailleVecteur)
{
   srand(time(NULL));

   int i;
   int index, temp;
   for (i = tailleVecteur - 1; i > 0; i--)
   {
      index = rand() % (i+1);
      temp = sourceVecteur[index];
      sourceVecteur[index] = sourceVecteur[i];
      sourceVecteur[i] = temp;
   }
}


/* libération de la mémoire allouée au réseau de neurones */
void destroyRNandMore(Net * net) {
    int i,j ;

    for(i=0;i<net->map_x;i++){
        for (j= 0 ; j < net->map_y; j++) {
          free(net->RN[i][j]);

        }

        free(net->activation_map[i]) ;
        free(net->RN[i]) ;
    }


    free(net->activation_map) ;
    free(net->RN);
}






void destroyRN(Net * net) {
    int i,j ;
    for(i=0;i<net->map_x;i++){
        for (j= 0 ; j < net->map_y; j++) {
            free(net->RN[i][j]);
        }
        free(net->RN[i]) ;
    }
    free(net->RN);
}




/* libération de la mémoire allouée à la matrice d'apprentissage */
void destroyData(Data * data) {
    int i ;
    for (i= 0 ; i < data->N_L_input_V ; i++) free(data->vecApprentissage[i]) ;
    free(data->vecApprentissage) ;
    printf("data freed\n") ;
}



/* retourne un entier (positif) aléatoire borné*/
int int_aleatoire_borne(int max) {
    return rand() % max;
}


/* retourne un entier aléatoire */
int int_aleatoire(void) {
    return rand();
}



